package bancosys.tec.security.impl;

import java.io.Serializable;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;

import bancosys.tec.persist.controller.TransactionalController;
import bancosys.tec.persist.dao.BaseDAOFactory;
import bancosys.tec.security.SecurityManager;
import bancosys.tec.security.SecurityService;
import bancosys.tec.security.authorization.AbstractPermission;
import bancosys.tec.security.authorization.GroupPermission;
import bancosys.tec.security.authorization.Permission;
import bancosys.tec.security.impl.dao.CredentialDAO;
import bancosys.tec.security.impl.dao.UserDAO;
import bancosys.tec.security.impl.domain.Credential;
import bancosys.tec.security.impl.domain.Group;
import bancosys.tec.security.impl.domain.User;
import bancosys.tec.security.impl.permission.parser.PermissionParser;

/**
 * @author piercio
 * @param <C> tipo de contexto de autenticação
 */
public abstract class AbstractSecurityManager<C> implements SecurityManager<C>, BeanFactoryAware {

    private static final PermissionParser[] ALL_PARSERS = {};

    private BeanFactory beanFactory;

    private PermissionParser[] parsers;

    private SecurityService securityService;

    private TransactionalController transactionalController;

    private UserDataLogger userDataLogger;

    /**
     * @param functionName String the funcion name
     * @return Permission
     */
    protected List<Permission> parsePermission(String functionName) {
        List<Permission> parsed = new ArrayList<Permission>();
        for (PermissionParser parser : this.getParsers()) {
            Permission p = parser.parse(functionName);
            if (p != null) {
                if (p instanceof AbstractPermission) {
                    ((AbstractPermission) p).setSystemId(this.getSecurityService().getSystemId());
                }
                parsed.add(p);
            }
        }
        return parsed;
    }

    /**
     * @param permissions String the funcion name
     * @return Permission
     */
    protected List<Permission> parsePermission(List<String> permissions) {
        List<Permission> parsed = new ArrayList<Permission>();
        for (String functionName : permissions) {
            for (PermissionParser parser : this.getParsers()) {
                Permission p = parser.parse(functionName);
                if (p != null) {
                    if (p instanceof AbstractPermission) {
                        ((AbstractPermission) p).setSystemId(this.getSecurityService().getSystemId());
                    }
                    parsed.add(p);
                }
            }
        }
        return parsed;
    }

    /**
     * @return the parsers
     */
    protected PermissionParser[] getParsers() {
        if (this.parsers == null) {
            PermissionParser[] fromBeanFactory;
            if (this.beanFactory instanceof ListableBeanFactory) {
                String[] names =
                        BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory, PermissionParser.class);
                fromBeanFactory = new PermissionParser[names.length];
                for (int i = 0; i < names.length; i++) {
                    fromBeanFactory[i] = (PermissionParser) this.beanFactory.getBean(names[i], PermissionParser.class);
                }
            } else {
                fromBeanFactory = new PermissionParser[0];
            }
            this.parsers = initializeParsers(fromBeanFactory);
        }
        return this.parsers;
    }

    /**
     * Inicializa os {@link PermissionParser} default, adicionando os passados
     * 
     * @param fromBeanFactory array de {@link PermissionParser}
     * @return {@link PermissionParser}
     */
    private static PermissionParser[] initializeParsers(PermissionParser[] fromBeanFactory) {
        PermissionParser[] parsers = new PermissionParser[ALL_PARSERS.length + fromBeanFactory.length];
        System.arraycopy(ALL_PARSERS, 0, parsers, 0, ALL_PARSERS.length);
        System.arraycopy(fromBeanFactory, 0, parsers, ALL_PARSERS.length, fromBeanFactory.length);
        return parsers;
    }

    /**
     * @return um novo usuario
     */
    protected User createUser() {
        return this.getDaoFactory().getDAOByClass(UserDAO.class).createBean();
    }

    /**
     * @return {@link Map} String -> Group
     */
    protected Map<String, Group> loadAllGroups() {
        List<Group> list = this.getDaoFactory().getGenericDAO(Group.class).findAll();
        Map<String, Group> map = new HashMap<String, Group>(list.size());
        for (Group group : list) {
            map.put(group.getName(), group);
        }
        return map;
    }

    /**
     * @param groupNames os nomes dos grupos
     * @return as credenciais associadas aos grupos
     */
    protected List<Credential> loadCredentialsByGroupNames(List<String> groupNames) {
        List<Credential> credentials = this.getDaoFactory().getDAOByClass(CredentialDAO.class).findByGroupNames(groupNames);

        return credentials;
    }

    /**
     * Converte as credenciais do user em permissoes no subject.
     * 
     * @param user user
     * @return set de permissoes.
     */
    protected Set<? extends Permission> getPubCredentials(User user) {
        Set<Permission> pubCredentials = new HashSet<Permission>();
        for (Credential credential : user.getAllCredentials()) {
            if (!this.getSecurityService().isSecurityClientActive(credential.getClient().getId())) {
                continue;
            }
            pubCredentials.add(credential.createPermission());
        }
        Set<Group> groups = user.getGroups();
        for (Group group : groups) {
            pubCredentials.add(new GroupPermission(group.getName()));
        }
        return pubCredentials;
    }

    /**
     * Registra o login na aplicação cliente de um dado usuário caso o Logger esteja definido.
     * 
     * @param sessionId o id da sessão.
     * @param username o nome do usuário.
     * @param clientname o nome da aplicação cliente.
     */
    protected void logUserLoginData(String sessionId, String username, String clientname) {
        if (this.userDataLogger != null) {
            this.userDataLogger.login(username, sessionId, clientname);
        }
    }

    /**
     * Registra o logout na aplicação cliente de um dado usuário caso o Logger esteja definido.
     * 
     * @param sessionId o id da sessão.
     * @param username o nome do usuário.
     * @param clientname o nome da aplicação cliente.
     */
    protected void logUserLogoutData(String sessionId, String username, String clientname) {
        if (this.userDataLogger != null) {
            this.userDataLogger.logout(username, sessionId, clientname);
        }
    }

    /**
     * @return the daoFactory
     */
    private BaseDAOFactory getDaoFactory() {
        return this.transactionalController.getDAOFactory();
    }

    /**
     * @return the securityService
     */
    public SecurityService getSecurityService() {
        return this.securityService;
    }

    /**
     * @param securityService the securityService to set
     */
    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    /**
     * @return the beanFactory
     */
    public BeanFactory getBeanFactory() {
        return this.beanFactory;
    }

    /**
     * @param beanFactory the beanFactory to set
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    /**
     * @param parsers the parsers to set
     */
    public void setParsers(PermissionParser[] parsers) {
        this.parsers = parsers;
    }

    /**
     * @return the transactionalController
     */
    protected TransactionalController getTransactionalController() {
        return this.transactionalController;
    }

    /**
     * @param transactionalController the transactionalController to set
     */
    public void setTransactionalController(TransactionalController transactionalController) {
        this.transactionalController = transactionalController;
    }

    /**
     * @return o valor para o campo userDataLogger
     */
    public UserDataLogger getUserDataLogger() {
        return this.userDataLogger;
    }

    /**
     * @param userDataLogger o valor para o campo userDataLogger
     */
    public void setUserDataLogger(UserDataLogger userDataLogger) {
        this.userDataLogger = userDataLogger;
    }

    /**
     * @author takeshi
     * @deprecated usar {@link SecurityPrincipal} no lugar
     */
    @Deprecated
    public static final class SimplePrincipal implements Principal, Serializable {

        private final String name;

        /**
         * @param name String
         */
        public SimplePrincipal(String name) {
            super();
            this.name = name;
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.security.Principal#getName()
         */
        public String getName() {
            return this.name;
        }

        /**
         * {@inheritDoc}
         * 
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "[PRINCIPAL name=" + this.name + "]";
        }
    }
}
